<?php


namespace app\common\library;

use think\Config;
use think\Exception;

/**
 * 单例模式微信支付分类
 * Class WechatChapter
 * @package app\common\library
 */
class WechatChapter
{
    //配置文件
    private $config;

    //存储单例变量
    private static $instance;

    const KEY_LENGTH_BYTE = 32;
    const AUTH_TAG_LENGTH_BYTE = 16;

    /**
     * 初始化方法
     * WechatChapter constructor.
     */
    public function __construct()
    {
        $config = Config::get('wechatchapter');
        $this->config = $config;
    }

    /**
     * 禁止克隆
     */
    private function __clone()
    {
    }

    /**
     * 获取实例
     * @return WechatChapter
     */
    public static function getInstance()
    {
        if (!self::$instance instanceof self) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * 微信免押创建订单
     * @param string $outOrderNo 在微信支付的时候,微信服务器生成的订单流水号,在支付通知中有返回
     * @param string $location 服务位置 可为空
     * @param string $description 付费项目名称 必填
     * @param int $amount 风险金额，也就是微信支付分最高扣款金额(单位:元)
     * @return array
     * @throws Exception
     */
    public function createOrder($outOrderNo = '', $location = '', $description = '', $amount = 1)
    {
        $refundorder = array(
            'appid' => $this->config['appid'],
            'out_order_no' => $outOrderNo,
            'service_id' => $this->config['service_id'],
            'service_introduction' => $this->config['start_service_introduction'],
            'time_range' => array(
                'start_time' => 'OnAccept',
            ),
            'location' => array(
                'start_location' => $location,
            ),
            'post_payments' => array(
                array(
                    'name' => $this->config['start_service_introduction'],
                    'description' => $description,
                ),
            ),
            'risk_fund' => array(
                'name' => 'ESTIMATE_ORDER_COST',
                'amount' => $amount * 100,
            ),
            'notify_url' => $this->config['notify_url'],
            'need_user_confirm' => true,
        );
        //请求数据,进行退款
        $xmldata = json_encode($refundorder);
        $url = 'https://api.mch.weixin.qq.com/v3/payscore/serviceorder';
        $res = $this->curl_post_ssl($url, $xmldata, '/v3/payscore/serviceorder');
        trace("========================创建支付分免押订单反馈数据==============================" . $res);
        if (!$res) {
            throw new Exception('微信服务器无响应');
        }
        $content = json_decode($res, true);
        if (isset($content['code']) && $content['code'] == 'NO_AUTH') {

        }
        if (isset($content['result_code'])) {
            if (strval($content['result_code']) == 'FAIL') {
                throw new Exception(strval($content['err_code']) . ':' . strval($content['err_code_des']));
            }
        }
        if (isset($content['return_code']) && strval($content['return_code']) == 'FAIL') {
            throw new Exception(strval($content['return_msg']));
        }
        if (isset($content['code']) && $content['code'] == 'PARAM_ERROR') {
            throw new Exception($content['message']);
        }
        /**
         * 创建确认订单数据
         * @param $package
         * @param $outOrderNo
         * @return array
         */
        $createConfirmOrder = function ($package, $outOrderNo) {
            $refundorder = array(
                'mch_id' => $this->config['mch_id'],//微信支付分配的商户号
                'package' => $package,//可在【创建订单】接口的返回字段package中获取
                'timestamp' => time(),//生成签名时间戳，单位秒
                'nonce_str' => self::getNonceStr(),//生成签名随机串。由数字、大小写字母组成，长度不超过32位
                'sign_type' => 'HMAC-SHA256',//签名类型。仅支持HMAC-SHA256
                'out_order_no' => $outOrderNo,
                //'service_id'=>$this->config['service_id']
            );
            //使用字段mch_id、package、timestamp、nonce_str、sign_type按照签名生成算法计算得出的签名值
            $refundorder['sign'] = self::getSign($refundorder, $this->config['key']);
            return $refundorder;
        };
        $returnData = $createConfirmOrder($content['package'], $outOrderNo);
        return $returnData;
    }

    /**
     * 结束订单
     * @param $outOrderNo 微信免押订单单号
     * @param $serviceEndLocation 归还地点
     * @param $totalAmount 支付总金额
     * @param $description 计费说明
     * @return array|bool|mixed|string
     */
    public function completeOrder($outOrderNo, $serviceEndLocation, $totalAmount,$description)
    {
        $real_end_time = date('YmdHis', time());
        //2009年12月25日9点10分10秒表示为20091225091010 20190806150809
        $refundorder = array(
            'appid' => $this->config['appid'],
            'service_id' => $this->config['service_id'],
            'post_payments' => array(
                array(
                    'name' => $this->config['end_service_introduction'],
                    'amount' => round($totalAmount * 100),
                    'description'=>$description,
                ),
            ),
            'time_range' => array(
                'end_time' => $real_end_time,
            ),
            'location' => array(
                'end_location' => $serviceEndLocation,
            ),
            'total_amount' => round($totalAmount * 100),
        );
        $xmldata = json_encode($refundorder);
        $url = 'https://api.mch.weixin.qq.com/v3/payscore/serviceorder/' . $outOrderNo . '/complete';
        trace("========================开始处理结束订单8-3-28-2-1 结束免押订单=============================={$url}===========" . json_encode($refundorder));
        $res = $this->curl_post_ssl($url, $xmldata, '/v3/payscore/serviceorder/' . $outOrderNo . '/complete');
        trace("========================开始处理结束订单8-3-28-2-2 结束免押订单返回结果=========================================" . $res);
        if (!$res) {
            return array(
                'status' => 0,
                'msg' => "请求操作失败"
            );
        }
        $content = $res;
        $content = json_decode($content, true);
        if (isset($content['code']) && $content['code'] == 'PARAM_ERROR') {
            return array(
                'status' => 0,
                'msg' => $content['message']
            );
        }
        if ($content['state'] == 'DOING') {
            $content['return_code'] = 'SUCCESS';
            $content['result_code'] = 'SUCCESS';
            $content['transaction_id'] = $content['order_id'];
        }
        if (isset($content['result_code'])) {
            if (strval($content['result_code']) == 'FAIL') {
                return array(
                    'status' => 0,
                    'msg' => strval($content['err_code']) . ':' . strval($content['err_code_des'])
                );
            }
        }
        if (strval($content['return_code']) == 'FAIL') {
            return array(
                'status' => 0, 'msg' => strval($content['return_msg'])
            );
        }
        $content['msg'] = '调用微信支付分扣款成功';
        $content['status'] = 1;
        return $content;
    }



    /*
      接口版本号 	version 	是 	String(32) 	1.0 	接口版本号，默认值1.0
      公众账号ID 	appid 	是 	String(32) 	wxd678efh567hg6787 	微信公众平台分配的与传入的商户号建立了支付绑定关系的appid，可在公众平台查看绑定关系. 需要在本系统先进行配置. 需要与创建订单时保持一致.
      商户号 	mch_id 	是 	String(32) 	1230000109 	微信支付分配的商户号. 需要在本系统先进行配置. 需要与创建订单时保持一致.
      随机字符串 	nonce_str 	是 	String(32) 	ibuaiVcKdpRxkhJA 	随机字符串，长度要求在32位以内。推荐随机数生成算法
      签名 	sign 	是 	String(128) 	029B52F67573D7E3BE74904BF9AEA 	通过签名算法计算得出的签名值，详见签名生成算法
      签名类型 	sign_type 	是 	String(32) 	HMAC-SHA256 	签名类型，仅支持HMAC-SHA256
      商户服务订单号 	out_order_no 	是 	String(32) 	1234323JKHDFE1243252 	商户系统内部订单号（不是交易单号），要求32个字符内，只能是数字、大小写字母_-|* 且在同一个商户号下唯一。详见商户订单号 需要和创建租借订单的商户服务订单号一致
      服务ID 	service_id 	是 	String(32) 	  	该服务ID有本接口对应产品的权限. 需要与创建订单时保持一致.
      是否归还 	returned 	是 	String(32) 	TRUE 	是否归还 已归还:TRUE、 没归还:FALSE
      实际归还时间 	real_end_time 	否 	String(14) 	20091225091010 	【是否归还】字段填写TRUE时必须填写, 否则不允许填写。订单生成时间，支持两种格式:yyyyMMddHHmmss和yyyyMMdd，如2009年12月25日9点10分10秒表示为20091225091010或者20091225.传入20091225默认认为时间为2009年12月25日23点59分59秒
      归还地点 	service_end_location 	否 	String(30) 	嗨客时尚主题展餐厅 	支持30个字符，按照字符计算 超过长度报错处理 表示用户归还物品的地点，线下相关服务可填写
      总金额 	total_amount 	是 	Int 	40000 	总金额,单位:分 不能超过创建订单时候的押金金额 总金额需要等于租金金额和赔偿金金额之和
      租金费用 	rent_fee 	二选一 	Int 	5800 	赔偿金费用和租金金额二者必须填写其中一个。赔偿金费用和赔偿金费用说明必须一起填写，单位：分 必须>=0，=0代表不需要扣费
      赔偿金费用 	compensation_fee 	Int 	6000 	赔偿金费用和租金金额二者必须填写其中一个。赔偿金费用和赔偿金费用说明必须一起填写，单位：分 必须>0，传入表示需要扣赔偿金 传入表示需要扣赔偿金 物品已归还——用户对物品产生了破坏行为造成的损失费用。 物品未归还——未还物品费用。 传入为0报错.
      赔偿金费用说明 	compensation_fee_desc 	String(30) 	充电宝有轻微损坏，赔偿30%押金 	支持30个字符，按照字符计算 超过长度报错返回
      完结凭证 	finish_ticket 	是 	String(512) 	  	用于完结订单时传入,确保订单完结时数据完整. 需要确保和确认订单接口完全一致
      需对字段再做处理。
      （注意：该字段可通过查询订单或者用户确认使用服务回调里获取finish_ticket，finish_ticket是支付分用来校验的参数，商户不可自定义）
      分账标识 	profit_sharing 	是 	String(16) 	Y 	Y-是，需要分账
      N-否，不分账
      字母要求大写，不传默认不分账
     * */

    //结束订单

    public function toendwxfreeorder($outOrderNo, $serviceEndLocation, $totalAmount)
    {
        $real_end_time = date('YmdHis', time());
        //2009年12月25日9点10分10秒表示为20091225091010 20190806150809
        $refundorder = array(
            'appid' => $this->config['appid'],
            'service_id' => $this->config['service_id'],
            'post_payments' => array(
                array(
                    'name' => $this->config['service_introduction'],
                    'amount' => round($totalAmount * 100),
                ),
            ),
            'time_range' => array(
                'end_time' => $real_end_time,
            ),
            'location' => array(
                'end_location' => $serviceEndLocation,
            ),
            'total_amount' => round($totalAmount * 100),
        );

        $xmldata = json_encode($refundorder);
        $url = 'https://api.mch.weixin.qq.com/v3/payscore/serviceorder/' . $outOrderNo . '/complete';
        $res = $this->curl_post_ssl($url, $xmldata, '/v3/payscore/serviceorder/' . $outOrderNo . '/complete');

        if (!$res) {
            return array(
                'status' => 0,
                'msg' => "请求操作失败"
            );
        }
        $content = $res;
        $content = json_decode($content, true);
        if ($content['state'] == 'DOING') {
            $content['return_code'] = 'SUCCESS';
            $content['result_code'] = 'SUCCESS';
            $content['transaction_id'] = $content['order_id'];
        }
        if (isset($content['result_code'])) {
            if (strval($content['result_code']) == 'FAIL') {
                return array(
                    'status' => 0,
                    'msg' => strval($content['err_code']) . ':' . strval($content['err_code_des'])
                );
            }
        }
        if (strval($content['return_code']) == 'FAIL') {
            return array(
                'status' => 0, 'msg' => strval($content['return_msg'])
            );
        }
        return $content;
    }


    /*
     * 公众账号ID 	appid 	是 	String(32) 	wx8888888888888888 	微信分配的公众账号ID（企业号corpid即为此appId）
      商户号 	mch_id 	是 	String(32) 	1900000109 	微信支付分配的商户号
      随机字符串 	nonce_str 	是 	String(32) 	5K8264ILTKCH16CQ2502SI8ZNMTM67VS 	随机字符串，不长于32位。推荐随机数生成算法
      签名 	sign 	是 	String(32) 	C380BEC2BFD727A4B6845133519F3AD6 	签名，详见签名生成算法
      签名类型 	sign_type 	否 	String(32) 	HMAC-SHA256 	签名类型，目前支持HMAC-SHA256和MD5，默认为MD5
      微信订单号 	transaction_id 	二选一 	String(32) 	1217752501201407033233368018 	微信生成的订单号，在支付通知中有返回
      商户订单号 	out_trade_no 	String(32) 	1217752501201407033233368018 	商户系统内部订单号，要求32个字符内，只能是数字、大小写字母_-|*@ ，且在同一个商户号下唯一。
      商户退款单号 	out_refund_no 	是 	String(64) 	1217752501201407033233368018 	商户系统内部的退款单号，商户系统内部唯一，只能是数字、大小写字母_-|*@ ，同一退款单号多次请求只退一笔。
      订单金额 	total_fee 	是 	Int 	100 	订单总金额，单位为分，只能为整数，详见支付金额
      退款金额 	refund_fee 	是 	Int 	100 	退款总金额，订单总金额，单位为分，只能为整数，详见支付金额
      退款货币种类 	refund_fee_type 	否 	String(8) 	CNY 	退款货币类型，需与支付一致，或者不填。符合ISO 4217标准的三位字母代码，默认人民币：CNY，其他值列表详见货币类型
      退款原因 	refund_desc 	否 	String(80) 	商品已售完
     * */
    public function test1()
    {

        $transaction_id = '4200001810202304104886878542';
        $out_refund_no = time() . rand(1000, 9999);
        $total_fee = 1 * 100;
        $refund_fee = 1 * 100;
        $this->returnwxfreeorder($transaction_id, $out_refund_no, $total_fee, $refund_fee);
    }

    //退款订单
    public function returnwxfreeorder($transaction_id, $out_refund_no, $total_fee, $refund_fee)
    {
        $refundorder = array(
            'transaction_id' => $transaction_id,//原支付交易对应的微信订单号
            'out_refund_no' => $out_refund_no,//商户系统内部的退款单号，商户系统内部唯一
            'amount' => array(
                'refund' => round($refund_fee * 100),
                'total' => round($total_fee),
                'currency' => 'CNY',
            ),
            'notify_url' => $this->config['notify_url'],
        );


        $xmldata = json_encode($refundorder);
        $url = 'https://api.mch.weixin.qq.com/v3/refund/domestic/refunds';
        $res = $this->curl_post_ssl($url, $xmldata, '/v3/refund/domestic/refunds');


        return $res;
        file_put_contents("tui6.txt", print_r($res, true));
        if (!$res) {
            return array('status' => 0, 'msg' => "Can't connect the server");
        }
        $content = self::xml2array($res);
        $content = json_decode($content, true);
        if (isset($content['result_code'])) {
            if (strval($content['result_code']) == 'FAIL') {
                return json_encode(array('status' => 0, 'msg' => strval($content['err_code']) . ':' . strval($content['err_code_des'])));
            }
        }
        if (strval($content['return_code']) == 'FAIL') {
            return json_encode(array('status' => 0, 'msg' => strval($content['return_msg'])));
        }
        return json_encode($content);
    }

    /*
     * 字段名 	变量名 	必填 	类型 	示例值 	描述
      接口版本号 	version 	是 	String(32) 	1.0 	接口版本号，默认值1.0
      公众账号ID 	appid 	是 	String(32) 	wxd678efh567hg6787 	微信公众平台分配的与传入的商户号建立了支付绑定关系的appid，可在公众平台查看绑定关系 需要在本系统先进行配置. 需要与创建订单时保持一致.
      商户号 	mch_id 	是 	String(32) 	1230000109 	微信支付分配的商户号 需要在本系统先进行配置. 需要与创建订单时保持一致.
      随机字符串 	nonce_str 	是 	String(32) 	ibuaiVcKdpRxkhJA 	随机字符串，长度要求在32位以内。推荐随机数生成算法
      签名 	sign 	是 	String(128) 	029B52F67573D7E3BE74904BF9AEA 	通过签名算法计算得出的签名值，详见签名生成算法
      签名类型 	sign_type 	是 	String(32) 	HMAC-SHA256 	签名类型，仅支持HMAC-SHA256
      商户服务订单号 	out_order_no 	只允许二选一 	String(32) 	1234323JKHDFE1243252 	商户系统内部服务订单号（不是交易单号），，要求32个字符内，只能是数字、大小写字母_-|* 且在同一个商户号下唯一。详见商户订单号 需要和创建租借订单的商户服务订单号一致
      回跳查询id 	return_query_id 	String 	jm3WvLD7qZf2mSv8hLTmtig2nqVRkakfkgryG/OgW94= 	微信侧回跳到商户前端时用于查单的单据查询id.详见章节”小程序跳转接口.回跳商户接口”
      服务ID 	service_id 	是 	String(32) 	  	该服务ID有本接口对应产品的权限 需要与创建订单时保持一致.
     * */
    public function test2()
    {

        $return_query_id = '1000000000202304081815247941394';
        $this->toquerywxfreeorder($return_query_id);
    }

    //查询订单
    public function toquerywxfreeorder($return_query_id)
    {

        $refundorder = array(
            'appid' => $this->config['appid'],
            'query_id' => $return_query_id,
            'service_id' => $this->config['service_id'],
        );

//        $refundorder['sign'] = self::getSign($refundorder,$this->config['key']);
        //请求数据,进行退款
        $xmldata = json_encode($refundorder);
        $url = 'https://api.mch.weixin.qq.com/v3/payscore/serviceorder?service_id=' . $refundorder['service_id'] . '&query_id=' . $refundorder['query_id'] . '&appid=' . $refundorder['appid'] . '';
        $res = $this->curl_get_ssl($url, $xmldata, 'v3/payscore/serviceorder');
        if (!$res) {
            return array('status' => 0, 'msg' => "Can't connect the server");
        }
        $content = $res;
        dump($content);
        die;
        $content = json_decode($content, true);
//        file_put_contents("tui6.txt", $content);
        if (isset($content['result_code'])) {
            if (strval($content['result_code']) == 'FAIL') {
                return json_encode(array('status' => 0, 'msg' => strval($content['err_code']) . ':' . strval($content['err_code_des'])));
            }
        }
        if (strval($content['return_code']) == 'FAIL') {
            return json_encode(array('status' => 0, 'msg' => strval($content['return_msg'])));
        }
        return json_encode($content);
    }

    /*
     *   接口版本号 	version 	是 	String(32) 	1.0 	接口版本号，默认值1.0
      公众账号ID 	appid 	是 	String(32) 	wxd678efh567hg6787 	微信公众平台分配的与传入的商户号建立了支付绑定关系的appid，可在公众平台查看绑定关系 需要在本系统先进行配置. 需要与创建订单时保持一致.
      商户号 	mch_id 	是 	String(32) 	1230000109 	微信支付分配的商户号 需要在本系统先进行配置. 需要与创建订单时保持一致.
      随机字符串 	nonce_str 	是 	String(32) 	ibuaiVcKdpRxkhJA 	随机字符串，长度要求在32位以内。推荐随机数生成算法
      签名 	sign 	是 	String(128) 	029B52F67573D7E3BE74904BF9AEA 	通过签名算法计算得出的签名值，详见签名生成算法
      签名类型 	sign_type 	是 	String(32) 	HMAC-SHA256 	签名类型，仅支持HMAC-SHA256
      商户服务订单号 	out_order_no 	是 	String(32) 	1234323JKHDFE1243252 	商户系统内部订单号（不是交易单号），要求32个字符内，只能是数字、大小写字母_-|* 且在同一个商户号下唯一。详见商户订单号 需要和创建租借订单的商户服务订单号一致
      撤销原因 	reason 	是 	String(50) 	用户投诉 	支持50个字符，按照字符计算 超过长度报错处理
      服务ID 	service_id 	是 	String(32) 	  	该服务ID有本接口对应产品的权限 需要与创建订单时保持一致.
     * */

    //撤销订单
    public function tocancelwxfreeorder($out_order_no, $reason)
    {
        $refundorder = array(
            'appid' => $this->config['appid'],
            'reason' => $reason,
            'service_id' => $this->config['service_id'],
        );
        //请求数据,进行退款
        $xmldata = json_encode($refundorder);
        $url = 'https://api.mch.weixin.qq.com/v3/payscore/serviceorder/' . $out_order_no . '/cancel';
        $res = $this->curl_post_ssl($url, $xmldata, '/v3/payscore/serviceorder/' . $out_order_no . '/cancel');
        $content = $res;
        $content = json_decode($content, true);
        if (isset($content['result_code'])) {
            if (strval($content['result_code']) == 'FAIL') {
                return array('status' => 0, 'msg' => strval($content['err_code']) . ':' . strval($content['err_code_des']));
            }
        }
        if (isset($content['return_code']) && strval($content['return_code']) == 'FAIL') {
            return array('status' => 0, 'msg' => strval($content['return_msg']));
        }
        return $content;
    }

    //调取微信支付分签名
    public function newsign($params)
    {
        $sign = self::getSign($params, $this->config['key']);
        return $sign;
    }

    /**
     * 微信支付发起请求
     */
    protected function curl_post_ssl($url = '', $xmldata = '', $urlSprit = '', $second = 30, $aHeader = array())
    {

        if (!empty($urlSprit)) {
            $auth = self::getSign2($this->config['mch_id'], $this->config['serial_no'], $this->config['api_key'], $xmldata, $urlSprit);

            $aHeader = array('Content-Type:application/json', 'Accept:application/json', 'User-Agent:' . $this->config['user_agent'] . '', 'Accept-Language:zh-CN', 'Authorization:' . $auth . '');
        }


        $ch = curl_init();
        //超时时间
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        //这里设置代理，如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '10.206.30.98');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
        curl_setopt($ch, CURLOPT_SSLVERSION, 1);
        //第一种方法，cert 与 key 分别属于两个.pem文件
        //默认格式为PEM，可以注释
        curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
        curl_setopt($ch, CURLOPT_SSLCERT, $this->config['api_cert']);
        //默认格式为PEM，可以注释
        curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
        curl_setopt($ch, CURLOPT_SSLKEY, $this->config['api_key']);
//        //默认格式为PEM，可以注释
//        curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
//        curl_setopt($ch,CURLOPT_SSLCERT,'/www/web/zongheyingxiao_wsandos_com/public_html/extend/Wechat/cert/apiclient_cert.pem');
//        //默认格式为PEM，可以注释
//        curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
//        curl_setopt($ch,CURLOPT_SSLKEY,'/www/web/zongheyingxiao_wsandos_com/public_html/extend/Wechat/cert/apiclient_key.pem');
//            //默认格式为PEM，可以注释
//            curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
//            curl_setopt($ch,CURLOPT_SSLCERT,$config['api_cert']);
//            //默认格式为PEM，可以注释
//            curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
//            curl_setopt($ch,CURLOPT_SSLKEY,$config['api_key']);
//
        //curl_setopt($ch,CURLOPT_CAINFO,$config['rootca']);
        if (count($aHeader) >= 1) {
            curl_setopt($ch, CURLOPT_HTTPHEADER, $aHeader);
        }
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xmldata);
        $data = curl_exec($ch);
        if ($data) {
            curl_close($ch);
            return $data;
        } else {
            $error = curl_errno($ch);
            echo "call faild, errorCode:$error\n";
            curl_close($ch);
            return false;
        }
    }

    /**
     * 微信支付发起请求
     */
    protected function curl_get_ssl($url = '', $xmldata = '', $urlSprit = '', $second = 30, $aHeader = array())
    {
        if (!empty($urlSprit)) {
            $auth = self::getSign2($this->config['mch_id'], $this->config['serial_no'], $this->config['api_key'], $xmldata, $urlSprit);
            $aHeader = array('Content-Type:application/json', 'Accept:application/json', 'User-Agent:' . $this->config['user_agent'] . '', 'Accept-Language:zh-CN', 'Authorization:' . $auth . '');
        }
        $ch = curl_init();
        //超时时间
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        //这里设置代理，如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '10.206.30.98');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
        curl_setopt($ch, CURLOPT_SSLVERSION, 1);
        //第一种方法，cert 与 key 分别属于两个.pem文件
        //默认格式为PEM，可以注释
        curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
        curl_setopt($ch, CURLOPT_SSLCERT, $this->config['api_cert']);
        //默认格式为PEM，可以注释
        curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
        curl_setopt($ch, CURLOPT_SSLKEY, $this->config['api_key']);
//        //默认格式为PEM，可以注释
//        curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
//        curl_setopt($ch,CURLOPT_SSLCERT,'/www/web/zongheyingxiao_wsandos_com/public_html/extend/Wechat/cert/apiclient_cert.pem');
//        //默认格式为PEM，可以注释
//        curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
//        curl_setopt($ch,CURLOPT_SSLKEY,'/www/web/zongheyingxiao_wsandos_com/public_html/extend/Wechat/cert/apiclient_key.pem');
//            //默认格式为PEM，可以注释
//            curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
//            curl_setopt($ch,CURLOPT_SSLCERT,$config['api_cert']);
//            //默认格式为PEM，可以注释
//            curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
//            curl_setopt($ch,CURLOPT_SSLKEY,$config['api_key']);
//
        //curl_setopt($ch,CURLOPT_CAINFO,$config['rootca']);
        if (count($aHeader) >= 1) {
            curl_setopt($ch, CURLOPT_HTTPHEADER, $aHeader);
        }
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xmldata);
        $data = curl_exec($ch);
        if ($data) {
            curl_close($ch);
            return $data;
        } else {
            $error = curl_errno($ch);
            echo "call faild, errorCode:$error\n";
            curl_close($ch);
            return false;
        }
    }

    /**
     * 产生随机字符串，不长于32位
     * @param int $length
     * @return 产生的随机字符串
     */
    public static function getNonceStr($length = 32)
    {
        $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        $str = "";
        for ($i = 0; $i < $length; $i++) {
            $num = rand(0, 35);
            $str .= $chars[$num];
        }
        $str = strtoupper($str);
        return $str;
    }

    /**
     * 生成签名
     * @return 签名
     */
    protected function makeSign($data)
    {
        //获取微信支付秘钥
        $key = $this->config['key'];
        // 去空
        $data = array_filter($data);
        //签名步骤一：按字典序排序参数
        ksort($data);
        $string_a = http_build_query($data);
        $string_a = urldecode($string_a);
        //签名步骤二：在string后加入KEY
        //$config=$this->config;
        $string_sign_temp = $string_a . "&key=" . $key;
        //签名步骤三：MD5加密
        $sign = md5($string_sign_temp);
        // 签名步骤四：所有字符转为大写
        $result = strtoupper($sign);
        return $result;
    }

    /**
     * 将一个数组转换为 XML 结构的字符串
     * @param array $arr 要转换的数组
     * @param int $level 节点层级, 1 为 Root.
     * @return string XML 结构的字符串
     */
    protected function array2xml($arr, $level = 1)
    {
        $s = $level == 1 ? "<xml>" : '';
        foreach ($arr as $tagname => $value) {
            if (is_numeric($tagname)) {
                $tagname = $value['TagName'];
                unset($value['TagName']);
            }
            if (!is_array($value)) {
                $s .= "<{$tagname}>" . (!is_numeric($value) ? '<![CDATA[' : '') . $value . (!is_numeric($value) ? ']]>' : '') . "</{$tagname}>";
            } else {
                $s .= "<{$tagname}>" . $this->array2xml($value, $level + 1) . "</{$tagname}>";
            }
        }
        $s = preg_replace("/([\x01-\x08\x0b-\x0c\x0e-\x1f])+/", ' ', $s);
        return $level == 1 ? $s . "</xml>" : $s;
    }

    /**
     * 将xml转为array
     * @param string $xml xml字符串
     * @return array    转换得到的数组
     */
    protected function xml2array($xml)
    {
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $rs = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
        $result = json_encode($rs);
        return $result;
    }

    /**
     * 获取签名
     * @param $params
     * @param $key
     * @return string
     */
    public static function getSign($params, $key)
    {
//        $key='rlmm18679205537hxtc1867920553700';
        ksort($params, SORT_STRING);
        $unSignParaString = self::formatQueryParaMap($params, false);
//        $signStr = strtoupper(md5($unSignParaString . "&key=" . $key));
        $sig = strtoupper(hash_hmac("sha256", $unSignParaString . "&key=" . $key, $key));
        return $sig;
    }

    public static function getSign2($mch_id, $serial_no, $mch_private_key, $body, $canonical_url)
    {
        $http_method = 'POST';
        $timestamp = time() + 1;
        $nonce = self::getNonceStr();
        $mch_private_key = openssl_get_privatekey(file_get_contents($mch_private_key));
//        $url_parts = parse_url($_SERVER);
//        $canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
        $message = $http_method . "\n" .
            $canonical_url . "\n" .
            $timestamp . "\n" .
            $nonce . "\n" .
            $body . "\n";
        openssl_sign($message, $raw_sign, $mch_private_key, 'sha256WithRSAEncryption');
//        $raw_sign = strtoupper(hash_hmac("sha256",$message,'sha256WithRSAEncryption'));
        $sign = base64_encode($raw_sign);
        $serial_no = $serial_no;
        $merchant_id = $mch_id;
        $schema = 'WECHATPAY2-SHA256-RSA2048';
        $token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"', $merchant_id, $nonce, $timestamp, $serial_no, $sign);
        return $schema . ' ' . $token;
    }

    protected static function formatQueryParaMap($paraMap, $urlEncode = false)
    {
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v) {
            if ($urlEncode) {
                $v = urlencode($v);
            }
            $buff .= $k . "=" . $v . "&";
//            if (null != $v && "null" != $v) {
//
//            }
        }
        $reqPar = '';
        if (strlen($buff) > 0) {
            $reqPar = substr($buff, 0, strlen($buff) - 1);
        }
        return $reqPar;
    }

    /**
     * 【免押授权成功/免押结束扣钱完成/退款回调】回调处理
     * @param \Closure $handelFunction
     */
    public function handelNotify(\Closure $handelFunction)
    {
        $xml = file_get_contents("php://input");
        $data = json_decode($xml, true);
        $resource = $data['resource'];
        $result = $this->decryptToString($resource['associated_data'], $resource['nonce'], $resource['ciphertext']);
//        \think\Log::error("微信支付分回调数据" . $result);
        $result = json_decode($result, true);
        //免押授权成功
        if ($data['event_type'] == 'PAYSCORE.USER_CONFIRM' && isset($data['resource'])) {
            if ($result && isset($result['out_order_no'])) {
                if ($handelFunction($result)) {
                    self::sendSuccess();
                }
            }
        }
        //免押结束，扣钱完成
        if ($data['event_type'] == 'PAYSCORE.USER_PAID' && isset($data['resource'])) {
            if ($result && isset($result['out_order_no'])) {
                if ($handelFunction($result)) {
                    self::sendSuccess();
                }
            }
        }
        //退款回调
        if ($data['event_type'] == 'REFUND.SUCCESS' && isset($data['resource'])) {
            if ($result && isset($result['transaction_id'])) {
                if ($handelFunction($result)) {
                    self::sendSuccess();
                }
            }
        }
    }

    /**
     * 通知微信支付成功
     */
    private static function sendSuccess()
    {
        $result = [
            'return_code' => 'SUCCESS',
            'return_msg' => 'OK'
        ];
        die(xml($result));
    }


    /**
     * Decrypt AEAD_AES_256_GCM ciphertext
     *
     * @param string $associatedData AES GCM additional authentication data
     * @param string $nonceStr AES GCM nonce
     * @param string $ciphertext AES GCM cipher text
     *
     * @return string|bool      Decrypted string on success or FALSE on failure
     */
    public function decryptToString($associatedData, $nonceStr, $ciphertext)
    {
        $ciphertext = \base64_decode($ciphertext);
        if (strlen($ciphertext) <= self::AUTH_TAG_LENGTH_BYTE) {
            return false;
        }

        //        // ext-sodium (default installed on >= PHP 7.2)
        if (function_exists('\sodium_crypto_aead_aes256gcm_is_available') && \sodium_crypto_aead_aes256gcm_is_available()) {
            return \sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->config['key']);
        }

        // ext-libsodium (need install libsodium-php 1.x via pecl)
        if (function_exists('\Sodium\crypto_aead_aes256gcm_is_available') && \Sodium\crypto_aead_aes256gcm_is_available()) {
            return \Sodium\crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->config['key']);
        }

        // openssl (PHP >= 7.1 support AEAD)
        if (PHP_VERSION_ID >= 70100 && in_array('aes-256-gcm', \openssl_get_cipher_methods())) {
            $ctext = substr($ciphertext, 0, -self::AUTH_TAG_LENGTH_BYTE);
            $authTag = substr($ciphertext, -self::AUTH_TAG_LENGTH_BYTE);

            return \openssl_decrypt($ctext, 'aes-256-gcm', $this->config['key'], \OPENSSL_RAW_DATA, $nonceStr,
                $authTag, $associatedData);
        }


    }


}